/*
*  connection.h - Connection management functions
*  Copyright (c) 2005 Derek Ingrouville (dergro@users.sourceforge.net)
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU Library General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <stdlib.h>
#include <string.h>

#include "connection.h"


MsnConnection *
msn_connection_new (MsnSession *session)
{
	MsnConnection *conn = NULL;

	conn = g_new (MsnConnection, 1);
	conn->socket = NULL;
	conn->session = session;
	conn->channel = NULL;
	conn->trid = 0;
	conn->connect_cb = NULL;
	conn->disconnect_cb = NULL;
	conn->callbacks = msn_callbacks_new ();
	conn->events = msn_event_queue_new ();
	conn->connected = FALSE;

	return conn;
}

void
msn_connection_free (MsnConnection *conn)
{
	g_return_if_fail (conn != NULL);

	if (conn->connected)
		msn_connection_disconnect (conn);

	if (conn->callbacks != NULL)
		msn_callbacks_free (conn->callbacks);

	if (conn->events != NULL)
		msn_event_queue_free (conn->events);

	g_free (conn);

	return;
}

int
msn_connection_connect (MsnConnection *conn, const char *server, unsigned int port)
{
	Socket *socket = NULL;
	GIOChannel *channel = NULL;

	g_return_val_if_fail (conn != NULL, -1);
	g_return_val_if_fail (server != NULL, -1);
	g_return_val_if_fail (port > 0, -1);

	if (conn->connected)
		msn_connection_disconnect (conn);

	socket = socket_new ();
	socket_connect (socket, server, port, TRUE);
	if (!socket->connected)
		return -1;

	channel = g_io_channel_unix_new (socket->sock_fd);
	if (channel == NULL) {
		socket_close (socket);
		return -1;
	}

	/* \r\n is newline */
	g_io_channel_set_line_term (channel, "\r\n", -1);

	/* Connections might receive binary data, so new encoding */
	g_io_channel_set_encoding (channel, NULL, NULL);

	conn->socket = socket;
	conn->channel = channel;
	conn->trid = 0;
	conn->connected = TRUE;

	/* Call connect callback */
	if (conn->connect_cb != NULL)
		conn->connect_cb (conn);

	g_print ("connected to: %s:%u\n", server, port);
	return 0;
}

void
msn_connection_disconnect (MsnConnection *conn)
{
	g_return_if_fail (conn != NULL);

	if (!conn->connected)
		return;

	g_io_channel_shutdown (conn->channel, TRUE, NULL);
	g_io_channel_unref (conn->channel);
	socket_close (conn->socket);

	conn->trid = 0;
	conn->connected = FALSE;

	/* Call disconnect callback */
	if (conn->disconnect_cb != NULL)
		conn->disconnect_cb (conn);

	return;
}

void
msn_connection_set_connect_cb (MsnConnection *conn, MsnConnCB connect_cb)
{
	g_return_if_fail (conn != NULL);
	g_return_if_fail (connect_cb != NULL);

	conn->connect_cb = connect_cb;
}

void
msn_connection_set_disconnect_cb (MsnConnection *conn, MsnConnCB disconnect_cb)
{
	g_return_if_fail (conn != NULL);
	g_return_if_fail (disconnect_cb != NULL);
	conn->disconnect_cb = disconnect_cb;
}

int
msn_connection_send_cmd (MsnConnection *conn, const char *format, ...)
{
	va_list args;
	char *cmd = NULL;
	char *params = NULL;
	int ret;

	g_return_val_if_fail (conn != NULL, -1);
	g_return_val_if_fail (conn->connected, -1);
	g_return_val_if_fail (format != NULL, -1);

	va_start (args, format);
	cmd = g_strdup_vprintf (format, args);
	va_end (args);

	if (cmd == NULL)
		return -1;

	g_print ("SENT: %s", cmd);

	ret = socket_write (conn->socket, cmd, strlen(cmd));

	g_free (cmd);
	conn->trid++;

	if (ret > 0)
		return 0;

	return -1;
}

int
msn_connection_read (MsnConnection *conn, char *dest_buf, unsigned int size)
{
	GIOStatus status = G_IO_STATUS_NORMAL;
	GError *error = NULL;
	unsigned int chars_read = 0;

	g_return_val_if_fail (conn != NULL, -1);
	g_return_val_if_fail (conn->connected, -1);
	g_return_val_if_fail (dest_buf != NULL, -1);
	g_return_val_if_fail (size > 0, -1);

	while (chars_read < size) {
		gsize len = 0;

		status = g_io_channel_read_chars (conn->channel,
			dest_buf + chars_read,
			size - chars_read,
			&len,
			&error);


		if (status != G_IO_STATUS_NORMAL)
			if (status != G_IO_STATUS_AGAIN)
				break;

		chars_read += len;

	}

	g_assert (error == NULL);

	return chars_read;
}

MsnCommand *
msn_connection_recv_cmd (MsnConnection *conn)
{
	GIOStatus status;
	GError *error = NULL;
	MsnCommand *cmd = NULL;
	char *buff = NULL;
	char *line = NULL;
	char *newline = NULL;

	g_return_val_if_fail (conn != NULL, NULL);
	g_return_val_if_fail (conn->channel != NULL, NULL);

	status = g_io_channel_read_line (conn->channel, 
		&buff,
		NULL,
		NULL,
		NULL);

	if (buff == NULL)
		return NULL;

	if (status != G_IO_STATUS_NORMAL) {
		if (buff != NULL)
			g_free (buff);
		return NULL;
	}

	/* strip newline terminator from string "\r\n" */
	newline =  g_strrstr (buff, "\r\n");
	if (newline == NULL) {
		/* Invalid command */
		g_free (line);
		return NULL;
	}

	line = g_strndup (buff, newline - buff);

	cmd = msn_command_from_string (line);

	g_free (line);
	g_free (buff);

	return cmd;
}

MsnMessage *
msn_connection_recv_msg (MsnConnection *conn, char *header)
{
	/* example header:  MSG Hotmail Hotmail 425\r\n */

	MsnMessage *msnmsg = NULL;
	MsnCommand *cmd = NULL;
	char *msg = NULL;  /* final MSG */
	char *body = NULL; /* what we will try to receive */
	int len = 0;       /* len parameter in MSG header */
	int total_read = 0;
	int i = 0;

	g_return_val_if_fail (conn != NULL, NULL);
	g_return_val_if_fail (header != NULL, NULL);

	cmd = msn_command_from_string (header);
	if (cmd == NULL)
		return NULL;

	if (cmd->param_count == 0)
		return NULL;

	/* Check if we are actually dealing with a MSG header */
	if (strcmp (cmd->name, "MSG") != 0) {
		msn_command_free (cmd);
		return NULL;
	}

	/* length of payload is the last command in the header */
	len = atoi (cmd->params [cmd->param_count - 1]);

	body = g_new (char, len + 1);

	if (msn_connection_read (conn, body, len) <= 0) {
		g_free (body);
		msn_command_free (cmd);
		return NULL;
	}

	body [len] = '\0';

	msg = g_strconcat (header, "\r\n", body, NULL);

	g_free (body);
	msn_command_free (cmd);

	msnmsg = msn_message_from_string (msg);

	return msnmsg;
}
